#include <D3DX10math.h>
#include "BVD3D10Renderer.h"

struct Texture {
    ID3D10Resource *texture;
    ID3D10ShaderResourceView *srv;
    ID3D10RenderTargetView   *rtv;
    ID3D10DepthStencilView   *dsv;
    ID3D10ShaderResourceView **srvArray;
    ID3D10RenderTargetView   **rtvArray;
    ID3D10DepthStencilView   **dsvArray;
    DXGI_FORMAT texFormat;
    DXGI_FORMAT srvFormat;
    DXGI_FORMAT rtvFormat;
    DXGI_FORMAT dsvFormat;
    int width, height, depth;
    int arraySize;
    uint flags;
};

struct Constant {
    char *name;
    ubyte *data;
    int buffer;
};

int constantComp(const void *s0, const void *s1){
    return strcmp(((Constant *) s0)->name, ((Constant *) s1)->name);
}

struct Sampler {
    char *name;
    int vsIndex;
    int gsIndex;
    int psIndex;
};

int samplerComp(const void *s0, const void *s1){
    return strcmp(((Sampler *) s0)->name, ((Sampler *) s1)->name);
}

struct Shader {
    ID3D10VertexShader *vertexShader;
    ID3D10PixelShader *pixelShader;
    ID3D10GeometryShader *geometryShader;
    ID3D10Blob *inputSignature;

    ID3D10Buffer **vsConstants;
    ID3D10Buffer **gsConstants;
    ID3D10Buffer **psConstants;
    uint nVSCBuffers;
    uint nGSCBuffers;
    uint nPSCBuffers;

    Constant *constants;
    Sampler *textures;
    Sampler *samplers;

    uint nConstants;
    uint nTextures;
    uint nSamplers;

    ubyte **vsConstMem;
    ubyte **gsConstMem;
    ubyte **psConstMem;

    bool *vsDirty;
    bool *gsDirty;
    bool *psDirty;
};

struct VertexFormat {
    ID3D10InputLayout *inputLayout;
    uint vertexSize[MAX_VERTEXSTREAM];
};

struct VertexBuffer {
    ID3D10Buffer *vertexBuffer;
    long size;
};

struct IndexBuffer {
    ID3D10Buffer *indexBuffer;
    uint nIndices;
    uint singleIndexSize;
};

struct Technique {
    ID3D10EffectTechnique *technique;
};

struct Effect {
    ID3D10Effect* effect;
    EffectVariable* effectVariables;
    uint nEffectVariable;
};

struct SamplerState {
    ID3D10SamplerState *samplerState;
};

struct BlendState {
    ID3D10BlendState *blendState;
};

struct DepthState {
    ID3D10DepthStencilState *dsState;
};

struct RasterizerState {
    ID3D10RasterizerState *rsState;
};

D3D10_USAGE usage[] = {
    D3D10_USAGE_IMMUTABLE,
    D3D10_USAGE_DEFAULT,
    D3D10_USAGE_DYNAMIC,
};

BvD3D10Renderer::BvD3D10Renderer(ID3D10Device* d3ddev) : 
    device(d3ddev)
{

}

BvD3D10Renderer::~BvD3D10Renderer()
{
}

int BvD3D10Renderer::getFormatSize(const AttributeFormat format) const {
    static int formatSize[] = { 
        sizeof(float), sizeof(ushort), sizeof(ubyte) 
    };
    return formatSize[format];
}

ID3D10ShaderResourceView* BvD3D10Renderer::createSRVFromFile(
    const char* fileName, D3DX10_IMAGE_LOAD_INFO* pLoadInfo)
{
    ID3D10ShaderResourceView* srv = NULL;

    WCHAR* wStr= new WCHAR[MAX_PATH];
    mbstowcs(wStr, fileName, MAX_PATH);

    HRESULT hr = D3DX10CreateShaderResourceViewFromFile( 
        device, wStr, pLoadInfo, NULL, &srv, NULL );
    if (FAILED(hr))
    {
        ErrorMsg("Can't create shader resource view.");
    }
    return srv;
}


VertexBufferId BvD3D10Renderer::addVertexBuffer(
    const void *data, const uint size, const BufferAccess 
    bufferAccess, ID3D10Buffer** buffer)
{
    VertexBuffer vb;
    ZeroMemory(&vb, sizeof(vb));

    vb.size = size;

    D3D10_BUFFER_DESC desc;
    desc.Usage = usage[bufferAccess];
    desc.ByteWidth = size;
    desc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
    desc.CPUAccessFlags = (bufferAccess == DYNAMIC)
        ? D3D10_CPU_ACCESS_WRITE : 0;
    desc.MiscFlags = 0;

    D3D10_SUBRESOURCE_DATA vbData;
    vbData.pSysMem = data;
    vbData.SysMemPitch = 0;
    vbData.SysMemSlicePitch = 0;

    if (FAILED(device->CreateBuffer(
        &desc, data? &vbData : NULL, &(vb.vertexBuffer))))
    {
        ErrorMsg("Couldn't create vertex buffer");
        return VB_NONE;
    }

    vertexBufferList.push_back(vb);

    return vertexBufferList.size()-1;
}

void BvD3D10Renderer::setVertexBuffer(
    const int stream, const VertexFormatId vfId,
    const VertexBufferId vbId, const uint offset)
{
    setVertexFormat(vfId);
    uint strides[1];
    strides[0] = vertexformatList[vfId].vertexSize[stream];
    device->IASetVertexBuffers(stream, 1, &vertexBufferList[vbId].vertexBuffer,
        strides, &offset);
}

IndexBufferId BvD3D10Renderer::addIndexBuffer(
    const void *data, const uint singleIndexSize, const uint nIndices, 
    const BufferAccess bufferAccess)
{
    IndexBuffer ib;
    ZeroMemory(&ib, sizeof(ib));
    ib.nIndices = nIndices;
    ib.singleIndexSize = singleIndexSize;

    D3D10_BUFFER_DESC desc;
    desc.Usage = usage[bufferAccess];
    desc.ByteWidth = nIndices * singleIndexSize;
    desc.BindFlags = D3D10_BIND_INDEX_BUFFER;
    desc.CPUAccessFlags = (bufferAccess == DYNAMIC)
        ? D3D10_CPU_ACCESS_WRITE : 0;
    desc.MiscFlags = 0;

    D3D10_SUBRESOURCE_DATA ibData;
    ibData.pSysMem = data;
    ibData.SysMemPitch = 0;
    ibData.SysMemSlicePitch = 0;
    if( FAILED(device->CreateBuffer( 
        &desc, data? &ibData : NULL, &ib.indexBuffer )))
    {
        ErrorMsg("Couldn't create vertex buffer");
        return IB_NONE;
    }

    indexBufferList.push_back(ib);
    return indexBufferList.size()-1;
}

void BvD3D10Renderer::setIndexBuffer(IndexBufferId ibId)
{
    if (ibId == IB_NONE){
        device->IASetIndexBuffer(NULL, DXGI_FORMAT_UNKNOWN, 0);
    } else {
        DXGI_FORMAT format = indexBufferList[ibId].singleIndexSize <
            4 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT;
        device->IASetIndexBuffer(indexBufferList[ibId].indexBuffer, format, 0);
    }
}

EffectId BvD3D10Renderer::addEffect(const char* shaderPath)
{
    Effect eft;
    ZeroMemory(&eft, sizeof(eft));

    DWORD dwShaderFlags = D3D10_SHADER_ENABLE_STRICTNESS;

    WCHAR* wshaderPath= new WCHAR[MAX_PATH];
    mbstowcs(wshaderPath, shaderPath, MAX_PATH);

    HRESULT hr = D3DX10CreateEffectFromFile( wshaderPath, NULL, NULL, 
        "fx_4_0", dwShaderFlags, 0, device, NULL, NULL, &eft.effect, NULL, NULL );

    if( FAILED( hr ) )
    {
        ErrorMsg("The FX file cannot be located.  Please run this \
                  executable from the directory that contains the \
                  FX file.");
        return EF_NONE;
    }

    //get effect variables list
    D3D10_EFFECT_DESC eftDesc;
    ZeroMemory(&eftDesc, sizeof(eftDesc));

    eft.effect->GetDesc(&eftDesc);
    eft.nEffectVariable = eftDesc.GlobalVariables;
    eft.effectVariables = new EffectVariable[eftDesc.GlobalVariables];

    for (int i = 0; i < eftDesc.GlobalVariables; ++i)
    {
        EffectVariable* var = eft.effectVariables+i;
        ZeroMemory(var, sizeof(*var));

        ID3D10EffectVariable* pD3DEffectVariable = eft.effect->GetVariableByIndex(i);
        D3D10_EFFECT_VARIABLE_DESC eftVarDesc;
        pD3DEffectVariable->GetDesc(&eftVarDesc);

        var->d3d10EffectVariable = pD3DEffectVariable;
        var->varName = new char[strlen(eftVarDesc.Name)+1];
        strcpy(var->varName, eftVarDesc.Name);
    }

    effectList.push_back(eft);

    return effectList.size()-1;
}

VertexFormatId BvD3D10Renderer::addEffectVertexFormat(
    const FormatDesc *formatDesc, const uint nAttribs, EffectId eftId,
    const char* techName)
{
    D3D10_PASS_DESC PassDesc;
    ZeroMemory(&PassDesc, sizeof(PassDesc));

    HRESULT hr = effectList[eftId].effect->GetTechniqueByName(techName)->
        GetPassByIndex(0)->GetDesc(&PassDesc);
    if (FAILED(hr))
    {
        ErrorMsg("Can't get pass desc.");
        return VF_NONE;
    }

    return addVertexFormat(formatDesc, nAttribs, &PassDesc);
}


VertexFormatId BvD3D10Renderer::addMeshVertexFormat(
    const FormatDesc *formatDesc, const uint nAttribs, SubMesh& sm)
{
    D3D10_PASS_DESC PassDesc;
    ZeroMemory(&PassDesc, sizeof(PassDesc));

    HRESULT hr = effectList[sm.eftId].effect->GetTechniqueByName(
        sm.techName)->GetPassByIndex(0)->GetDesc(&PassDesc);
    if (FAILED(hr))
    {
        ErrorMsg("Can't get pass desc.");
        return VF_NONE;
    }

    sm.vfId = addVertexFormat(formatDesc, nAttribs, &PassDesc);
    return sm.vfId;
}

VertexFormatId BvD3D10Renderer::addVertexFormat(
    const FormatDesc *formatDesc, const uint nAttribs, 
    D3D10_PASS_DESC* pPassDesc)
{
    static const DXGI_FORMAT formats[][4] = {
        DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32G32_FLOAT, DXGI_FORMAT_R32G32B32_FLOAT, DXGI_FORMAT_R32G32B32A32_FLOAT,
        DXGI_FORMAT_R16_FLOAT, DXGI_FORMAT_R16G16_FLOAT, DXGI_FORMAT_UNKNOWN,         DXGI_FORMAT_R16G16B16A16_FLOAT,
        DXGI_FORMAT_R8_UNORM,  DXGI_FORMAT_R8G8_UNORM,   DXGI_FORMAT_UNKNOWN,         DXGI_FORMAT_R8G8B8A8_UNORM,
    };

    static const char *semanticsNames[] = {
        NULL,
        "Position",
        "Texcoord",
        "Normal",
        "Tangent",
        "Binormal",
        "Color",
    };

    int index[6] = {};

    VertexFormat vf;
    memset(vf.vertexSize, 0, sizeof(vf.vertexSize));

    D3D10_INPUT_ELEMENT_DESC *layoutDesc = 
        new D3D10_INPUT_ELEMENT_DESC[nAttribs];

    // Fill the vertex element array
    for (uint i = 0; i < nAttribs; i++){
        int stream = formatDesc[i].stream;
        int size = formatDesc[i].size;
        layoutDesc[i].InputSlot = stream;
        layoutDesc[i].AlignedByteOffset = vf.vertexSize[stream];
        layoutDesc[i].SemanticName = semanticsNames[formatDesc[i].type];
        layoutDesc[i].SemanticIndex = index[formatDesc[i].type]++;
        layoutDesc[i].Format = formats[formatDesc[i].format][size - 1];
        layoutDesc[i].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
        layoutDesc[i].InstanceDataStepRate = 0;

        vf.vertexSize[stream] += size * getFormatSize(formatDesc[i].format);
    }

    // Create the input layout
    HRESULT hr = device->CreateInputLayout( 
        layoutDesc, nAttribs, pPassDesc->pIAInputSignature,
        pPassDesc->IAInputSignatureSize, &vf.inputLayout );

    if( FAILED( hr ) )
    {
        ErrorMsg("Failed to set vertex input layout");
        return VF_NONE;
    }

    vertexformatList.push_back(vf);

    return vertexformatList.size()-1;
}

void BvD3D10Renderer::setVertexFormat(VertexFormatId vfId)
{
    if (vfId == VF_NONE)
        device->IASetInputLayout(NULL);
    else 
        device->IASetInputLayout(vertexformatList[vfId].inputLayout);
}

HRESULT BvD3D10Renderer::GetTechniquePassByIndexDesc(
    D3D10_PASS_DESC* pPassDesc,const int eftId, const char* techName, 
    const int indexId)
{
    HRESULT hr = effectList[eftId].effect->GetTechniqueByName(techName)->
        GetPassByIndex(indexId)->GetDesc(pPassDesc);
    
    return hr;
}

void BvD3D10Renderer::drawMeshes(
    const Primitives primitives, const int nIndices, const int firstIndex,
    const int nVertices, const int firstVertex)
{
    for (int i = 0; i < subMeshList.size(); ++i)
    {
        drawSubMesh(i, primitives, nIndices, firstIndex, nVertices, firstVertex);
    }
}


void BvD3D10Renderer::drawSubMesh(
    const SubMeshId smId, const Primitives primitives,const int nIndices, 
    const int firstIndex, const int nVertices, const int firstVertex)
{
    std::vector<SubMesh>::iterator it = subMeshList.begin()+smId;
    ID3D10EffectTechnique* tech = 
        effectList[it->eftId].effect->GetTechniqueByName(it->techName);
    D3D10_TECHNIQUE_DESC techDesc;
    tech->GetDesc( &techDesc );
    for( uint p = 0; p < techDesc.Passes; ++p )
    {
        tech->GetPassByIndex( p )->Apply( 0 );
        if (nVertices > 0)
        {
            setVertexBuffer(0, it->vfId, it->vbId, 0);
            drawVertexBuffer(primitives, nVertices, firstVertex);
        }
        if (nIndices > 0)
        {
            setVertexBuffer(0, it->vfId, it->vbId, 0);
            setIndexBuffer(it->ibId);
            drawIndexBuffer(primitives, nIndices, firstIndex);
        }
    }
}

SubMeshId BvD3D10Renderer::addSubMesh(SubMesh sb)
{
    subMeshList.push_back(sb);
    return subMeshList.size()-1;
}

void BvD3D10Renderer::drawVertexBuffer(
    const Primitives primitives, const int nVertices, const int firstVertex)
{
    device->IASetPrimitiveTopology(d3dPrim[primitives]);
    device->Draw(nVertices, firstVertex);
   
}

void BvD3D10Renderer::drawIndexBuffer(
    const Primitives primitives, const int nIndices, const int firstIndex)
{
    device->IASetPrimitiveTopology(d3dPrim[primitives]);
    device->DrawIndexed(nIndices, firstIndex, 0);

}

ID3D10Effect* BvD3D10Renderer::GetEffectById(EffectId eftId)
{
    return effectList[eftId].effect;
}


EffectVariable* BvD3D10Renderer::getEffectVariableByName(
    const EffectId eftId, const char* varName, 
    const EffectVariableType type)
{
    EffectVariable* ret = new EffectVariable;
    ZeroMemory(ret, sizeof(EffectVariable));
    ret->isDirty = true;
    ret->type = type;

    switch (type)
    {
    case EFFECT_VARIABLE_SCALAR_FLOAT:
        break;
    case EFFECT_VARIABLE_VECTOR_FLOAT:
        break;
    case EFFECT_VARIABLE_MATRIX:
        ret->d3d10EffectVariable = effectList[eftId].effect->
            GetVariableByName(varName);
        break;
    default:
        ErrorMsg("Effect variable type can't recognize");
    }
    return ret;
} 

void BvD3D10Renderer::setEffectVariable(
    const EffectVariable* pEffectVariable, float* pData)
{
    switch (pEffectVariable->type)
    {
    case EFFECT_VARIABLE_SCALAR_FLOAT:
        break;
    case EFFECT_VARIABLE_VECTOR_FLOAT:
        break;
    case EFFECT_VARIABLE_MATRIX:
        pEffectVariable->d3d10EffectVariable->AsMatrix()->SetMatrix(pData);
        break;
    default:
        ErrorMsg("Effect variable type can't recognize");
    }
}

bool BvD3D10Renderer::setEffectVariableByName(
    const EffectId eftId, const char* varName, 
    const EffectVariableType type, void* pData, 
    const int offset, const uint nData)
{
    //TODO: this work can be implemented
    Effect* ev = &effectList[eftId];
    for (int i = 0; i < ev->nEffectVariable; ++i)
    {
        if (!strcmp(varName, ev->effectVariables[i].varName))
        {
            switch (type)
            {
            case EFFECT_VARIABLE_SCALAR_FLOAT:
                ev->effectVariables[i].d3d10EffectVariable->AsScalar()->
                    SetFloat(*(float*)pData);
                return true;
            case EFFECT_VARIABLE_VECTOR_FLOAT:
                ev->effectVariables[i].d3d10EffectVariable->AsVector()->
                    SetFloatVector((float*)pData);
                return true;
            case EFFECT_VARIABLE_VECTOR_FLOATARRAY:
                ev->effectVariables[i].d3d10EffectVariable->AsVector()->
                    SetFloatVectorArray((float*)pData, offset, nData);
                return true;
            case EFFECT_VARIABLE_MATRIX:
                ev->effectVariables[i].d3d10EffectVariable->AsMatrix()->
                    SetMatrix((float*)pData);
                return true;
            case EFFECT_VARIABLE_SHADERRESOURCE:
                ev->effectVariables[i].d3d10EffectVariable->AsShaderResource()->
                    SetResource((ID3D10ShaderResourceView*)pData);
                return true;
            default:
                ErrorMsg("Effect variable type can't recognize");
            }
        }
    }
    return false;
}